package com.taxi.comm.server.mina.filter;

import java.util.Iterator;

import net.sf.json.JSONObject;
import net.sf.json.JSONSerializer;

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang.StringUtils;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.apache.mina.filter.codec.demux.MessageDecoderAdapter;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.taxi.comm.business.SpringContext;

import com.taxi.comm.server.Configuration;
import com.taxi.comm.util.BytesUtils;
import com.taxi.comm.util.ZipUtils;

/**
 * Decoder for parsing the bytes stream, and construct a request instance.
 * The decoder will first merge the bytes stream to confirm that the message is
 * completely received. If the message is compressed (this specified in the fifth
 * byte), we unzip the compressed byte array to original one, Then we use the 
 * given CharSet (e.g. UTF-8) to decode it to java.lang.String. At last we use
 * JSON-Lib to parse the JSON string, and convert it to the Request object. 
 * 
 * @author Watertao
 *
 */
public class PMSMessageDecoder extends MessageDecoderAdapter {

	private static final Logger log = LoggerFactory.getLogger(PMSMessageDecoder.class);
	
	private String encoding;
	
	public PMSMessageDecoder() {
		
		Configuration config = Configuration.getInstance();
		
		encoding = config.getEncoding(); 
		
	}

	/**
	 * Whether the message is completely received or not.
	 * We may received a message within 2 parts or more (may be the read buffer is small, 
	 * or network issue), when we receive the message and we found the actual length
	 * of the received message is not satisfied with the length specified in message itself,
	 * we should wait for more data. 
	 * If the actual length of the received message is larger than specified in message,
	 * we consider it was a funny message and refused to handle it.
	 * Consider another situation, that if the first part is received , but we realize that
	 * more data should be sent, so we wait for the subsequence data, but the data is not
	 * sent as we think for a long time, we should abandon it (see the read idle time defined
	 * in configuration file). 
	 */
	@Override
	public MessageDecoderResult decodable(IoSession session, IoBuffer in) {
		try {
			log.warn("RemoteAddress"+session.getRemoteAddress());
			if (!isMessageComplete(in)) {
				return MessageDecoderResult.NEED_DATA;
			} else {
				return MessageDecoderResult.OK;
			}
		} catch (Exception e) {
			return MessageDecoderResult.NOT_OK;
		}
		
		
	}

	/**
	 * Retrieve the content part of the message (from and include the sixth byte).
	 * Unzip it if it was compressed.
	 * Decode it to String with the given charset.
	 * Parse the with the JSON rule.
	 * Return the constructed object.
	 */
	@Override
	public MessageDecoderResult decode(IoSession session, IoBuffer in,
			ProtocolDecoderOutput out) throws Exception {
		
		// Try to decode body
		String m = decodeBody(session, in);

		// Return NEED_DATA if the body is not fully read.
		if (m == null) {
			return MessageDecoderResult.NOT_OK;
		}

		out.write(m);

		return MessageDecoderResult.OK;
	}

	/**
	 * Check if the whole message is received. For the connection problem, maybe the 
	 * message will be sent in 2 parts.
	 * 
	 * @param in
	 * @return
	 * @throws Exception
	 */
	private boolean isMessageComplete(IoBuffer in) throws Exception {
		
		// check the whether the message length is larger than 5 (4 length + 1 compress)
		
		/*if (in.remaining() < 5) {
			log.warn("Message length is less than 5, more data needed.");
			return false;
		}*/
		// Get the first 4 bytes, and convert it to integer
		byte[] lenArry = new byte[4];
		in.get(lenArry);
		
	
	
		int expectedLength = BytesUtils.byteArrayToInt(lenArry);
		
		// Get the length of the content, the whole bytes - 5 (4 length + 1 compress)
		int actualLength = in.limit() - 5;
		
		if (actualLength < expectedLength) {
			log.warn("The length specified in header [" + expectedLength + 
					"] is not satisfied with the actual content length [" 
					+ actualLength + "]. more data needed.");
			
			return false;
		}
		
		log.warn("actualLength:"+actualLength+" expectedLength:"+expectedLength);
		if (actualLength > expectedLength) {
			log.warn("actualLength > expectedLength");
			throw new Exception("The length of content is larger than specified in header.");
		}
		
		return true;
	}

	
	private String decodeBody(IoSession session, IoBuffer in) throws Exception  {
		
		in.position(4);
		
		boolean isCompressed = in.get() == 1;
		
		session.setAttribute("IS_COMPRESSED", isCompressed);
		
		byte[] contentBytes = new byte[in.limit() - 5];
		in.get(contentBytes);
		
		
		if (isCompressed) {
			contentBytes = ZipUtils.UnZip(contentBytes);
		}
		
		log.info("encode:"+encoding);
		String content = new String(contentBytes, encoding);
		log.info("<Request>: " + content);
		
		
		
			/*JSONObject json = (JSONObject) JSONSerializer.toJSON(content,
				JsonContext.getJsonConfig());
		
			if (json != null) {
				String code = (String) json.get("code");
				if (StringUtils.isEmpty(code)) {
					throw new Exception("no code value specified.");
				}
			

				/*Class requestClass = Class.forName("com.huateng.pms.comm.business.service.p" + code + ".P" + code + "Request");*/
			
				//request = (S) JSONObject.toBean(json, requestClass);
		
		return content;
			
		
		
		
	}

	
	public static void main(String[] args) {
		
		
	}
	
	 

}
